package Utility;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import Utility.ChatServer.ConnectionThread;


public aspect ChatServerAspect implements Encryption {
	
	private Logger logger;
	private final int minThrottlingGap = 500;	// Minimum allowed time between messages.
	private final int maxFloodLimit = 5;		// Maximum number of times a user can flood before action.
	private Map<String, Integer> userWarnings;
	private ArrayList<String> warnedUsers;
	
	public ChatServerAspect(){
		logger = Logger.getLogger("ChatServer");
		userWarnings = new HashMap<String, Integer>();
		warnedUsers = new ArrayList<String>();
	}
	
	/** 
	 * Encrypt all outgoing messages.
	 * @param s Raw string to be encrypted.
	 */
	pointcut encryptMessage(String s):
		call (void *.sendMessage(String)) && args(s);
	
	/**
	 * Encrypt outgoing message, logging before and after results.
	 * @param s String to be encrypted.
	 */
	void around(String s) : encryptMessage(s){
		logger.fine("Out(raw): " + s);
		s = encrypt(s);
		logger.fine("Out (encrypted) " + s);
		proceed(s);
	}
	
	/** 
	 * Decrypt all incoming messages.
	 * @param s Encrypted string to be decoded.
	 */
	pointcut decryptMessage(String s):
		call (void *.handleRequest(String)) && args(s);
	
	/**
	 * Decrypt incoming message, logging before and after results.
	 * @param s Encrypted string to be decoded.
	 */
	void around(String s) : decryptMessage(s){
		logger.finer("In(raw): " + s);
		s = decrypt(s);
		logger.fine("In (decoded): " + s);
		proceed(s);
	}
	
	/**
	 * Check for flooding of incoming messages and perform corrective actions if flooding has occurred.
	 * @param name Username of the message sender.
	 * @param timeLastMessage The timestamp of the previous message received from client.
	 */
	pointcut checkForFlood(long lastMessageTime, ConnectionThread connThread):
		call (void *.ConnectionThread.resetTimeOfLastMessage(long, ConnectionThread)) && args(lastMessageTime, connThread);

	 after(long lastmessageTime, ConnectionThread connThread): checkForFlood(lastmessageTime, connThread){
		 long timeBetweenMessages = System.currentTimeMillis() - lastmessageTime;
		if (timeBetweenMessages < minThrottlingGap){
			issueFloodWarning(connThread);
		
		}
	}
		

	private void issueFloodWarning(ConnectionThread connThread) {
		// Create user entry if it does not yet exist.
		String name = connThread.getName();
		if(!userWarnings.containsKey(name)){
			userWarnings.put(name, 0);
		}
		
		int numWarnings = userWarnings.get(name);
		
		if (numWarnings > maxFloodLimit){
			issueKickWarning(name, connThread);
		}
		numWarnings++;
		userWarnings.put(name, numWarnings);
	}

	private void issueKickWarning(String name, ConnectionThread connThread) {
		if (warnedUsers.contains(name)){
			connThread.kickUser("User kicked for flooding. \n (Sit in the corner and think about what you've done)");
	}else{
		userWarnings.put(name, 0);
		warnedUsers.add(name);
		connThread.sendLockoutMessage();
	}
		
	}

	/**
	 * Encrypt a string using the method specified in the Encryption class.
	 */
	public String encrypt(String s){
		ChatEncryption encryption = new ChatEncryption();
		return encryption.encrypt(s);
	}
	
	/**
	 * Decrypt a string using the method specified in the Encryption class.
	 */
	public String decrypt(String s){
		ChatEncryption encryption = new ChatEncryption();
		return encryption.decrypt(s);
	}
}
